package com.zxq.common.utils;

import com.zxq.common.utils.internal.http.DefaultRetryIntercepter;
import okhttp3.*;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * HttpClient工具<br>
 * 使用场景：与外部公司合作时，调用外部http接口时使用 <br>
 * 提供执行get请求、post请求、文件上传、文件下载功能 <br>
 * 支持异步请求 <br>
 * 支持对单次请求自定义超时时间参数<br>
 * 响应结果非成功状态时，重试3次<br>
 * 使用示例：<br>
 *        private HttpUtil httpUtil = HttpUtil.getInstance();<br>
 *        Map<String, String> headerParams = new HashMap<>();<br>
 *        headerParams.put("Content-Type", "text/html;charset=utf-8");<br>
 *        String res = httpUtil.doGet("https://www.baidu.com", headerParams);<br>
 */
public class HttpUtil {
    private static final int CORE_SIZE = Runtime.getRuntime().availableProcessors();
    private static final int MAX_SIZE = CORE_SIZE * 2;
    private static final String DEFAULT_UPLOAD_FILE = "file";
    private static final String DEFAULT_UPLOAD_FILE_NAME = "upload_file";
    private static final String JSON_CONTENT_TYPE = "application/json";
    private static final String TEXT_CONTENT_TYPE = "text/plain";
    public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
    public static final MediaType URL_ENCODED = MediaType.parse("application/x-www-form-urlencoded; charset=utf-8");
    public static final MediaType FORM_DATA = MediaType.parse("multipart/form-data; charset=utf-8");
    public static final MediaType XML = MediaType.parse("text/xml; charset=utf-8");
    /**
     * 定义全局OkHttpClient对象
     */
    private static OkHttpClient httpClient = null;
    private static HttpUtil instance = null;

    // 单例OkHttp
    public static HttpUtil getInstance(){
        if (instance == null) {
            synchronized (HttpUtil.class){
                if (instance == null){
                    instance = new HttpUtil();
                }
            }
        }
        return instance;
    }

    private HttpUtil() {
        TrustManager[] trustManagers = buildTrustManagers();
        httpClient = new OkHttpClient.Builder()
            .connectTimeout(15, TimeUnit.SECONDS)
            .writeTimeout(20, TimeUnit.SECONDS)
            .readTimeout(60, TimeUnit.SECONDS)
            .connectionPool(new ConnectionPool(MAX_SIZE, 5, TimeUnit.MINUTES))
            .sslSocketFactory(createSSLSocketFactory(trustManagers), (X509TrustManager) trustManagers[0])
            .hostnameVerifier((hostName, session) -> true)
            .retryOnConnectionFailure(true)
            .addInterceptor(new DefaultRetryIntercepter(3))
            .build();
    }

    /**
     * 生成安全套接字工厂，用于https请求的证书跳过(信任所有证书)
     * @param trustAllCerts
     * @return
     */
    private static SSLSocketFactory createSSLSocketFactory(TrustManager[] trustAllCerts) {
        SSLSocketFactory ssfFactory = null;
        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, trustAllCerts, new SecureRandom());
            ssfFactory = sc.getSocketFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ssfFactory;
    }

    private static TrustManager[] buildTrustManagers() {
        return new TrustManager[] {
            new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) {
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[]{};
                }
            }
        };
    }

    /**
     * 执行get请求
     * @param url 请求url
     * @return 响应结果
     * @throws IOException
     */
    public String doGet(String url) throws IOException {
        return doGet(url, null, null);
    }

    /**
     * 执行get请求
     * @param url 请求url
     * @param headerParams header参数
     * @return 响应结果
     * @throws IOException
     */
    public String doGetWithHeaders(String url, Map<String, String> headerParams) throws IOException {
        return doGet(url, headerParams, null);
    }

    /**
     * 执行get请求
     * @param url 请求url
     * @param params 请求参数
     * @return 响应结果
     * @throws IOException
     */
    public String doGet(String url, Map<String, String> params) throws IOException {
        return doGet(url, null, params);
    }

    /**
     * 执行get请求
     * @param url 请求url
     * @param headerParams header参数
     * @param params 请求参数
     * @return 响应结果
     * @throws IOException
     */
    public String doGet(String url, Map<String, String> headerParams, Map<String, String> params) throws IOException {
        Request request = buildGetRequest(url, headerParams, params);
        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("HttpUtil doGet response not successful " + response);
            }
            assert response.body() != null;
            return response.body().string();
        }

    }

    /**
     * 异步执行get请求
     * @param url 请求url
     * @param headerParams header参数
     * @param params 请求参数
     * @param responseCallback 回调方法
     */
    public void doGetAsync(String url, Map<String, String> headerParams, Map<String, String> params, Callback responseCallback) {
        Request request = buildGetRequest(url, headerParams, params);
        httpClient.newCall(request).enqueue(responseCallback);
    }

    /**
     * 执行post请求
     * @param url 请求url
     * @param params 请求参数
     * @return 响应结果
     * @throws IOException
     */
    public String doPost(String url, Map<String, String> params) throws IOException {
        return doPost(url, null, params);
    }

    /**
     * 执行post请求
     * @param url 请求url
     * @param headerParams header参数
     * @param params 请求参数
     * @return 响应结果
     * @throws IOException
     */
    public String doPost(String url, Map<String, String> headerParams, Map<String, String> params) throws IOException {
        Request request = buildPostRequest(url, headerParams, params);
        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("HttpUtil doPost response not successful " + response);
            }
            assert response.body() != null;
            return response.body().string();
        }
    }

    /**
     * 异步执行post请求
     * @param url 请求url
     * @param headerParams header参数
     * @param params 请求参数
     * @param responseCallback 回调方法
     */
    public void doPostAsync(String url, Map<String, String> headerParams, Map<String, String> params, Callback responseCallback) {
        Request request = buildPostRequest(url, headerParams, params);
        httpClient.newCall(request).enqueue(responseCallback);
    }

    /**
     * 执行post请求
     * @param url 请求url
     * @param jsonParam json格式的请求参数
     * @return 响应结果
     * @throws IOException
     */
    public String doPost(String url, String jsonParam) throws IOException {
        return doPost(url, null, null, jsonParam, null);
    }

    /**
     * 执行post请求
     * @param url 请求url
     * @param headerParams header参数
     * @param jsonParam json格式的请求参数
     * @return 响应结果
     * @throws IOException
     */
    public String doPostWithHeaders(String url, Map<String, String> headerParams, String jsonParam) throws IOException {
        return doPost(url, headerParams, null, jsonParam, null);
    }

    /**
     * 执行post请求
     * @param url 请求url
     * @param headerParams header参数
     * @param params 请求参数
     * @param jsonParam json格式的请求参数
     * @return 响应结果
     * @throws IOException
     */
    public String doPost(String url, Map<String, String> headerParams, Map<String, String> params, String jsonParam) throws IOException {
        return doPost(url, headerParams, params, jsonParam, null);
    }

    /**
     * 执行post请求
     * @param url 请求url
     * @param params 请求参数
     * @param jsonParam json格式的请求参数
     * @return 响应结果
     * @throws IOException
     */
    public String doPost(String url, Map<String, String> params, String jsonParam) throws IOException {
        return doPost(url, null, params, jsonParam, null);
    }

    /**
     * 执行post请求
     * @param url 请求url
     * @param jsonParam json格式的请求参数
     * @param mediaType 请求体和响应体的内容格式，默认格式：{@link MediaType}.parse("application/json; charset=utf-8")
     * @return 响应结果
     * @throws IOException
     */
    public String doPost(String url, String jsonParam, MediaType mediaType) throws IOException {
        return doPost(url, null, null, jsonParam, mediaType);
    }

    /**
     * 执行post请求
     * @param url 请求url
     * @param headerParams header参数
     * @param jsonParam json格式的请求参数
     * @param mediaType 请求体和响应体的内容格式，默认格式：{@link MediaType}.parse("application/json; charset=utf-8")
     * @return 响应结果
     * @throws IOException
     */
    public String doPost(String url, Map<String, String> headerParams, String jsonParam, MediaType mediaType) throws IOException {
        return doPost(url, headerParams, null, jsonParam, mediaType);
    }

    /**
     * 执行post请求
     * @param url 请求url
     * @param headerParams header参数
     * @param params 请求参数
     * @param jsonParam json格式的请求参数
     * @param mediaType 请求体和响应体的内容格式，默认格式：{@link MediaType}.parse("application/json; charset=utf-8")
     * @return 响应结果
     * @throws IOException
     */
    public String doPost(String url, Map<String, String> headerParams, Map<String, String> params, String jsonParam, MediaType mediaType) throws IOException {

        Request request = buildPostRequest(url, headerParams, params, jsonParam, mediaType);
        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("HttpUtil doPost response not successful " + response);
            }
            assert response.body() != null;
            return response.body().string();
        }
    }

    /**
     * 异步执行post请求
     * @param url 请求url
     * @param headerParams header参数
     * @param params 请求参数
     * @param jsonParam json格式的请求参数
     * @param mediaType 请求体和响应体的内容格式，如：{@link MediaType}.parse("application/json; charset=utf-8")
     * @param responseCallback 回调方法
     */
    public void doPostAsync(String url, Map<String, String> headerParams, Map<String, String> params, String jsonParam, MediaType mediaType, Callback responseCallback) {
        Request request = buildPostRequest(url, headerParams, params, jsonParam, mediaType);
        httpClient.newCall(request).enqueue(responseCallback);
    }

    /**
     * 上传文件
     * @param url 请求url
     * @param headerParams header参数
     * @param file 需要上传的文件
     * @return 响应结果
     * @throws IOException
     */
    public String doUpload(String url, Map<String, String> headerParams, File file) throws IOException {
        return doUpload(url, headerParams, DEFAULT_UPLOAD_FILE_NAME, file);
    }

    /**
     * 上传文件
     * @param url 请求url
     * @param fileName 上传文件名
     * @param file 需要上传的文件
     * @return 响应结果
     * @throws IOException
     */
    public String doUpload(String url, String fileName, File file) throws IOException {
        return doUpload(url, null, fileName, file);
    }

    /**
     * 上传文件
     * @param url 请求url
     * @param headerParams header参数
     * @param fileName 上传文件名，默认为：upload_file
     * @param file 需要上传的文件
     * @return 响应结果
     * @throws IOException
     */
    public String doUpload(String url, Map<String, String> headerParams, String fileName, File file) throws IOException {
        fileName = fileName == null ? DEFAULT_UPLOAD_FILE_NAME : fileName;
        MultipartBody multipartBody = new MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart(DEFAULT_UPLOAD_FILE, fileName, RequestBody.create(FORM_DATA, file))
            .build();

        Request.Builder requestBuilder = new Request.Builder().url(url).post(multipartBody);

        // 添加请求头
        if (headerParams != null && !headerParams.isEmpty()) {
            Headers headers = Headers.of(headerParams);
            requestBuilder.headers(headers);
        }

        Request request = requestBuilder.build();
        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("HttpUtil doUpload response not successful " + response);
            }
            assert response.body() != null;
            return response.body().string();
        }
    }

    /**
     * 下载文件
     * @param url 请求url
     * @param params 请求参数
     * @param outFileName 保存文件名
     * @throws IOException
     */
    public void doDownload(String url, Map<String, String> params, String outFileName) throws IOException {
        doDownload(url, null, params, outFileName);
    }

    /**
     * 下载文件
     * @param url 请求url
     * @param headerParams header参数
     * @param params 请求参数
     * @param outFileName 保存文件名
     * @throws IOException
     */
    public void doDownload(String url, Map<String, String> headerParams, Map<String, String> params, String outFileName) throws IOException {

        Request request = buildGetRequest(url, headerParams, params);
        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("HttpUtil doDownload response not successful " + response);
            }
            assert response.body() != null;
            if (Objects.requireNonNull(response.body().contentType()).toString().toLowerCase().contains(JSON_CONTENT_TYPE)
                || Objects.requireNonNull(response.body().contentType()).toString().toLowerCase().contains(TEXT_CONTENT_TYPE)) {
                throw new IOException("HttpUtil doDownload wrong contentType, url" + url);
            }

            try (InputStream in = response.body().byteStream();
                 FileOutputStream out = new FileOutputStream(outFileName)) {
                int bufferSize = 2048;
                byte[] data = new byte[bufferSize];
                int length = 0;
                while ((length = in.read(data, 0, bufferSize)) > 0)
                {
                    out.write(data, 0, length);
                }
            }
        }
    }

    /**
     * 自定义超时时间参数执行请求
     * @param request {@link Request}请求，构造请参考<br>
     *     {@link HttpUtil#buildGetRequest(String, Map, Map)}; <br>
     *     {@link HttpUtil#buildGetRequest(String, Map, Map)}; <br>
     *     {@link HttpUtil#buildPostRequest(String, Map, Map)}; <br>
     *     {@link HttpUtil#buildPostRequest(String, Map, Map)}; <br>
     * @param connectTimeOut 连接超时时间
     * @param writeTimeOut 写超时时间
     * @param readTimeOut 读超时时间
     * @param timeUnit 时间单位{@link TimeUnit}
     * @return 响应结果
     * @throws IOException
     */
    public String customCall(Request request, long connectTimeOut, long writeTimeOut, long readTimeOut, TimeUnit timeUnit) throws IOException {
        OkHttpClient copyClient = httpClient.newBuilder()
            .connectTimeout(connectTimeOut, timeUnit)
            .writeTimeout(writeTimeOut, timeUnit)
            .readTimeout(readTimeOut, timeUnit)
            .build();

        try (Response response = copyClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("HttpUtil customCall response not successful " + response);
            }
            assert response.body() != null;
            return response.body().string();
        }
    }

    /**
     * 自定义拦截器执行请求
     * @param request {@link Request}请求，构造请参考<br>
     *     {@link HttpUtil#buildGetRequest(String, Map, Map)}; <br>
     *     {@link HttpUtil#buildGetRequest(String, Map, Map)}; <br>
     *     {@link HttpUtil#buildPostRequest(String, Map, Map)}; <br>
     *     {@link HttpUtil#buildPostRequest(String, Map, Map)}; <br>
     * @param customInterceptor {@link Interceptor} 自定义拦截器
     * @return 响应结果
     * @throws IOException
     */
    public String customCall(Request request, Interceptor customInterceptor) throws IOException {
        OkHttpClient copyClient = httpClient.newBuilder()
            .addInterceptor(customInterceptor)
            .build();

        try (Response response = copyClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("HttpUtil customCall response not successful " + response);
            }
            assert response.body() != null;
            return response.body().string();
        }
    }

    /**
     * 拼接url与请求参数
     * @param url 请求url 如：http://xxxxx.com
     * @param params 请求参数 如：{"param1":"value1","param2":"value2"}
     * @return 拼接后的url 如：http://xxxxx.com?param1=value1&param2=value2
     */
    public String buildUrl(String url, Map<String, String> params) {
        // 拼接URL与请求参数
        StringBuilder sb = new StringBuilder(url);
        if (params != null && !params.isEmpty()) {
            boolean firstFlag = true;
            for (Map.Entry entry : params.entrySet()) {
                if (firstFlag) {
                    sb.append("?").append(entry.getKey()).append("=").append(entry.getValue());
                    firstFlag = false;
                } else {
                    sb.append("&").append(entry.getKey()).append("=").append(entry.getValue());
                }
            }
        }
        return sb.toString();
    }

    /**
     * 构造get方法的{@link Request}请求
     * @param url 请求url
     * @param headerParams header参数
     * @param params 请求参数
     * @return get方法的Request请求
     */
    public Request buildGetRequest(String url, Map<String, String> headerParams, Map<String, String> params) {
        // 拼接URL与请求参数
        url = buildUrl(url, params);
        Request.Builder builder = new Request.Builder().url(url).get();

        // 添加请求头
        if (headerParams != null && !headerParams.isEmpty()) {
            Headers headers = Headers.of(headerParams);
            builder.headers(headers);
        }

        return builder.build();
    }

    /**
     * 构造post方法的{@link Request}请求（表单形式提交的请求参数）
     * @param url 请求url
     * @param headerParams header参数
     * @param params 请求参数
     * @return post方法的Request请求
     */
    public Request buildPostRequest(String url, Map<String, String> headerParams, Map<String, String> params) {
        FormBody.Builder formBodyBuilder = new FormBody.Builder();
        //添加参数
        if (params != null && !params.isEmpty()) {
            params.forEach(formBodyBuilder::add);
        }
        Request.Builder requestBuilder = new Request.Builder().url(url).post(formBodyBuilder.build());

        // 添加请求头
        if (headerParams != null && !headerParams.isEmpty()) {
            Headers headers = Headers.of(headerParams);
            requestBuilder.headers(headers);
        }

        return requestBuilder.build();
    }

    /**
     * 构造post方法的{@link Request}请求（RequestBody形式提交的请求参数）
     * @param url 请求url
     * @param headerParams header参数
     * @param params 请求参数
     * @param jsonParam json格式的请求参数
     * @param mediaType 请求体和响应体的内容格式，默认格式：{@link MediaType}.parse("application/json; charset=utf-8")
     * @return post方法的Request请求
     */
    public Request buildPostRequest(String url, Map<String, String> headerParams, Map<String, String> params, String jsonParam, MediaType mediaType) {
        // 拼接URL与请求参数
        url = buildUrl(url, params);
        mediaType = mediaType == null ? JSON : mediaType;
        RequestBody requestBody = RequestBody.create(mediaType, jsonParam);
        Request.Builder requestBuilder = new Request.Builder().url(url).post(requestBody);

        // 添加请求头
        if (headerParams != null && !headerParams.isEmpty()) {
            Headers headers = Headers.of(headerParams);
            requestBuilder.headers(headers);
        }

        return requestBuilder.build();
    }
}
